import logging
import json
import time
import binascii

import gevent

import util
from Crypt import CryptBitcoin
from Plugin import PluginManager
from Config import config
from util import helper
from Debug import Debug


@PluginManager.acceptPlugins
class User(object):
    def __init__(self, master_address=None, master_seed=None, data={}):
        if master_seed:
            self.master_seed = master_seed
            self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
        elif master_address:
            self.master_address = master_address
            self.master_seed = data.get("master_seed")
        else:
            self.master_seed = CryptBitcoin.newSeed()
            self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
        self.sites = data.get("sites", {})
        self.certs = data.get("certs", {})
        self.settings = data.get("settings", {})
        self.delayed_save_thread = None

        self.log = logging.getLogger("User:%s" % self.master_address)

    # Save to data/users.json
    @util.Noparallel(queue=True, ignore_class=True)
    def save(self):
        s = time.time()
        users = json.load(open("%s/users.json" % config.data_dir))
        if self.master_address not in users:
            users[self.master_address] = {}  # Create if not exist
        user_data = users[self.master_address]
        if self.master_seed:
            user_data["master_seed"] = self.master_seed
        user_data["sites"] = self.sites
        user_data["certs"] = self.certs
        user_data["settings"] = self.settings
        helper.atomicWrite("%s/users.json" % config.data_dir, helper.jsonDumps(users).encode("utf8"))
        self.log.debug("Saved in %.3fs" % (time.time() - s))
        self.delayed_save_thread = None

    def saveDelayed(self):
        if not self.delayed_save_thread:
            self.delayed_save_thread = gevent.spawn_later(5, self.save)

    def getAddressAuthIndex(self, address):
        return int(binascii.hexlify(address.encode()), 16)

    @util.Noparallel()
    def generateAuthAddress(self, address):
        s = time.time()
        address_id = self.getAddressAuthIndex(address)  # Convert site address to int
        auth_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, address_id)
        self.sites[address] = {
            "auth_address": CryptBitcoin.privatekeyToAddress(auth_privatekey),
            "auth_privatekey": auth_privatekey
        }
        self.saveDelayed()
        self.log.debug("Added new site: %s in %.3fs" % (address, time.time() - s))
        return self.sites[address]

    # Get user site data
    # Return: {"auth_address": "xxx", "auth_privatekey": "xxx"}
    def getSiteData(self, address, create=True):
        if address not in self.sites:  # Generate new BIP32 child key based on site address
            if not create:
                return {"auth_address": None, "auth_privatekey": None}  # Dont create user yet
            self.generateAuthAddress(address)
        return self.sites[address]

    def deleteSiteData(self, address):
        if address in self.sites:
            del(self.sites[address])
            self.saveDelayed()
            self.log.debug("Deleted site: %s" % address)

    def setSiteSettings(self, address, settings):
        site_data = self.getSiteData(address)
        site_data["settings"] = settings
        self.saveDelayed()
        return site_data

    # Get data for a new, unique site
    # Return: [site_address, bip32_index, {"auth_address": "xxx", "auth_privatekey": "xxx", "privatekey": "xxx"}]
    def getNewSiteData(self):
        import random
        bip32_index = random.randrange(2 ** 256) % 100000000
        site_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, bip32_index)
        site_address = CryptBitcoin.privatekeyToAddress(site_privatekey)
        if site_address in self.sites:
            raise Exception("Random error: site exist!")
        # Save to sites
        self.getSiteData(site_address)
        self.sites[site_address]["privatekey"] = site_privatekey
        self.save()
        return site_address, bip32_index, self.sites[site_address]

    # Get BIP32 address from site address
    # Return: BIP32 auth address
    def getAuthAddress(self, address, create=True):
        cert = self.getCert(address)
        if cert:
            return cert["auth_address"]
        else:
            return self.getSiteData(address, create)["auth_address"]

    def getAuthPrivatekey(self, address, create=True):
        cert = self.getCert(address)
        if cert:
            return cert["auth_privatekey"]
        else:
            return self.getSiteData(address, create)["auth_privatekey"]

    # Add cert for the user
    def addCert(self, auth_address, domain, auth_type, auth_user_name, cert_sign):
        # Find privatekey by auth address
        auth_privatekey = [site["auth_privatekey"] for site in list(self.sites.values()) if site["auth_address"] == auth_address][0]
        cert_node = {
            "auth_address": auth_address,
            "auth_privatekey": auth_privatekey,
            "auth_type": auth_type,
            "auth_user_name": auth_user_name,
            "cert_sign": cert_sign
        }
        # Check if we have already cert for that domain and its not the same
        if self.certs.get(domain) and self.certs[domain] != cert_node:
            return False
        elif self.certs.get(domain) == cert_node:  # Same, not updated
            return None
        else:  # Not exist yet, add
            self.certs[domain] = cert_node
            self.save()
            return True

    # Remove cert from user
    def deleteCert(self, domain):
        del self.certs[domain]

    # Set active cert for a site
    def setCert(self, address, domain):
        site_data = self.getSiteData(address)
        if domain:
            site_data["cert"] = domain
        else:
            if "cert" in site_data:
                del site_data["cert"]
        self.saveDelayed()
        return site_data

    # Get cert for the site address
    # Return: { "auth_address":.., "auth_privatekey":.., "auth_type": "web", "auth_user_name": "nofish", "cert_sign":.. } or None
    def getCert(self, address):
        site_data = self.getSiteData(address, create=False)
        if not site_data or "cert" not in site_data:
            return None  # Site dont have cert
        return self.certs.get(site_data["cert"])

    # Get cert user name for the site address
    # Return: user@certprovider.bit or None
    def getCertUserId(self, address):
        site_data = self.getSiteData(address, create=False)
        if not site_data or "cert" not in site_data:
            return None  # Site dont have cert
        cert = self.certs.get(site_data["cert"])
        if cert:
            return cert["auth_user_name"] + "@" + site_data["cert"]
